namespace ScrumSprintMonitor.Implementation
{
	#region #using Directives

	using System;
	using System.IO;
	using System.Threading;

	using Infrastructure;

	#endregion

	public class FileDataStreamProvider : IDataStreamProvider,
	                                      IDisposable
	{
		#region Private Fields

		private readonly string filePath;
		private readonly ILogger<FileDataStreamProvider> logger;
		private readonly SynchronizationContext syncContext;
		private bool disposed;
		private FileSystemWatcher fileSystemWatcher;
		private int streamChangeInQueue;

		#endregion

		public FileDataStreamProvider(string filePath, ILogger<FileDataStreamProvider> logger)
		{
			this.filePath = filePath;
			this.logger = logger;
			this.syncContext = SynchronizationContext.Current;
		}

		#region Implementation of IFileStreamProvider

		public Stream OpenStream(bool writable)
		{
			if (this.disposed)
			{
				throw new ObjectDisposedException(GetType().Name);
			}

			if (this.fileSystemWatcher == null)
			{
				this.fileSystemWatcher = new FileSystemWatcher(Path.GetDirectoryName(this.filePath), Path.GetFileName(this.filePath));
				this.fileSystemWatcher.Changed += fileSystemWatcher_Changed;
				this.fileSystemWatcher.EnableRaisingEvents = true;
			}

			if (writable)
			{
				return new FileStream(this.filePath, FileMode.Truncate, FileAccess.Write, FileShare.None);
			}

			return new FileStream(this.filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
		}

		public string Location
		{
			get { return this.filePath; }
		}

		public bool IsReadOnly
		{
			get { return (File.GetAttributes(this.filePath) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly; }
		}

		public event EventHandler StreamChanged;

		#endregion

		private void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
		{
			Interlocked.Increment(ref this.streamChangeInQueue);

			this.syncContext.Post(fullPath => OnStreamChanged(e.FullPath), null);
		}

		private void OnStreamChanged(string fullPath)
		{
			Thread.Sleep(0);

			if (Interlocked.Decrement(ref this.streamChangeInQueue) == 0)
			{
				if (StreamChanged != null)
				{
					if (File.Exists(this.filePath))
					{
						try
						{
							StreamChanged(this, EventArgs.Empty);
						}
						catch (IOException ex)
						{
							this.logger.Warn(ex, "Exception detected while reacting to configuration file change '{0}'.", fullPath);
						}
					}
				}
			}
		}

		#region Implementation of IDisposable

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		/// <filterpriority>2</filterpriority>
		public void Dispose()
		{
			if (!this.disposed)
			{
				this.disposed = true;

				if (this.fileSystemWatcher != null)
				{
					this.fileSystemWatcher.Dispose();
				}
			}
		}

		#endregion
	}
}